package com.cloudfun.campusshare.code.generate.definition;

import com.cloudfun.campusshare.code.generate.CodeGenerator;
import com.cloudfun.campusshare.code.generate.JavaFileDefType;
import lombok.Data;
import lombok.SneakyThrows;
import org.apache.commons.collections4.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by Huqin on 2020/5/26
 */
@Data
public class JavaFileDefinition {

    public static final String INDENTS = "    ";

    private final JavaFileDefType type;
    private String pkg;
    private Set<Class> imports = new HashSet<>();
    private ClassCommentDefinition commentDefinition = ClassCommentDefinition.get();
    private List<AnnotationDefinition> annotations = new ArrayList<>();
    private List<String> lombokAnnotations = new ArrayList<>();
    private final boolean isInterface;
    private final Class entityClazz;
    private Class superClass;
    private List<String> implClasses = new ArrayList<>();

    private String simpleClassName;
    private String className;

    private List<FieldDefinition> fields = new ArrayList<>();

    private List<MethodDefinition> methodDefinitions = new ArrayList<>();

    public JavaFileDefinition(JavaFileDefType type, boolean isInterface, Class entityClazz) {
        this.type = type;
        this.isInterface = isInterface;
        this.entityClazz = entityClazz;
    }

    public String getFile() {
        this.init();
        String packageFolder = getPkg();
        while (packageFolder.contains(".")) {
            packageFolder = packageFolder.replace(".", "\\");
        }
        packageFolder += "\\";
        return CodeGenerator.SRC_CODE_FOLDER + packageFolder + simpleClassName + ".java";
    }

    private boolean inited = false;

    public void init() {
        if (inited) {
            return;
        }
        inited = true;
        if (null == type) {
            throw new RuntimeException("type can not be null!");
        }
        if (null == pkg) {
            String entityPkgName = entityClazz.getPackage().getName();
            pkg = entityPkgName.substring(0, entityPkgName.lastIndexOf(".")) + type.getPackageSuffix();
        }
        // lombok注解
        if (type == JavaFileDefType.DTO || type == JavaFileDefType.VO || type == JavaFileDefType.LIST_VO) {
            lombokAnnotations.add("lombok.Data");
        }
        if (type == JavaFileDefType.SERVICE_IMPL || type == JavaFileDefType.CONTROLLER) {
            lombokAnnotations.add("lombok.extern.slf4j.Slf4j");
        }
        this.initImports();
        if (null == entityClazz) {
            throw new RuntimeException("entity class can not be null!");
        }

        this.simpleClassName = getEntityOriginName(entityClazz) + type.getFileNameSuffix();
        this.className = pkg + "." + simpleClassName;
    }

    @SneakyThrows
    private void initImports() {
        for (AnnotationDefinition annotation : annotations) {
            imports.add(annotation.getClazz());
        }
        if (null != superClass) {
            imports.add(superClass);
        }
        for (FieldDefinition fieldDefinition : fields) {
            imports.add(fieldDefinition.getClazz());
            imports.addAll(fieldDefinition.getAnnotations().stream().map(AnnotationDefinition::getClazz).collect(Collectors.toList()));
        }
        for (MethodDefinition methodDefinition : methodDefinitions) {
            Class retClazz = methodDefinition.getRetClazz();
            if (!retClazz.equals(Void.class)) {
                imports.add(retClazz);
            }
            for (Class parameter : methodDefinition.getParameters()) {
                imports.add(parameter);
            }
        }
        for (String implClass : implClasses) {
            if (implClass.contains("<")) {
                implClass = implClass.substring(0, implClass.indexOf("<"));
            }
            imports.add(CodeGenerator.JAVA_FILE_CLASS_LOADER.loadClass(implClass));
        }
        imports.removeIf(importClazz -> {
            if (null != importClazz.getPackage()) {
                return importClazz.getPackage().getName().startsWith("java.lang");
            } else {
                // 自定义类加载器加载的类找不到Package
                return false;
            }
        });
    }

    public void addAnnotation(AnnotationDefinition annotationDefinition) {
        annotations.add(annotationDefinition);
    }

    public void addField(FieldDefinition field) {
        fields.add(field);
    }

    public void addMethod(MethodDefinition methodDefinition) {
        methodDefinitions.add(methodDefinition);
    }

    public static String getEntityOriginName(Class entityClazz) {
        return entityClazz.getSimpleName().replace("Entity", "");
    }

    @SneakyThrows
    public String toString() {
        this.init();
        StringBuilder sb = new StringBuilder("package " + pkg + ";\n");
        sb.append("\n");

        imports.forEach(importClazz -> sb.append("import ").append(importClazz.getName()).append(";").append("\n"));
        // 导入lombok注解
        lombokAnnotations.forEach(lombokAnnotation -> sb.append("import ").append(lombokAnnotation).append(";").append("\n"));
        sb.append("\n");
        // 注释
        String comment = commentDefinition.toString();
        sb.append(comment);

        if (CollectionUtils.isNotEmpty(lombokAnnotations) || CollectionUtils.isNotEmpty(annotations)) {
            // lombok注解
            lombokAnnotations.forEach(lombokAnnotation -> {
                if (lombokAnnotation.contains(".")) {
                    lombokAnnotation = lombokAnnotation.substring(lombokAnnotation.lastIndexOf(".") + 1);
                }
                sb.append("@").append(lombokAnnotation).append("\n");
            });
            // 注解
            String annotationDefinitions = annotations.stream().map(AnnotationDefinition::toString).collect(Collectors.joining("\n"));
            sb.append(annotationDefinitions);
            sb.append("\n");
        }

        sb.append("public ");
        if (isInterface) {
            sb.append("interface ");
            sb.append(simpleClassName).append(" ");
            if (CollectionUtils.isNotEmpty(implClasses)) {
                sb.append("extends ");
                List<String> simpleImplClass = implClasses.stream().map(implClass -> {
                    if (implClass.contains(".")) {
                        implClass = implClass.substring(implClass.lastIndexOf(".") + 1);
                    }
                    return implClass;
                }).collect(Collectors.toList());
                String impls = String.join(",", simpleImplClass);
                sb.append(impls);
            }
        } else {
            sb.append("class ");
            sb.append(simpleClassName).append(" ");
            if (null != superClass) {
                sb.append("extends ").append(superClass.getName());
            }
            if (CollectionUtils.isNotEmpty(implClasses)) {
                sb.append("implements ");
                List<String> simpleImplClass = implClasses.stream().map(implClass -> {
                    if (implClass.contains(".")) {
                        implClass = implClass.substring(implClass.lastIndexOf(".") + 1);
                    }
                    return implClass;
                }).collect(Collectors.toList());
                String impls = String.join(",", simpleImplClass);
                sb.append(impls);
            }
        }
        sb.append("{").append("\n");
        sb.append("\n");
        // 属性定义开始
        if (!isInterface) {
            fields.forEach(field -> {
                sb.append(field.toString());
                sb.append("\n");
//                sb.append("\n");
//                sb.append("@").append(Autowired.class.getSimpleName()).append("\n");
//                sb.append("    ").append("private ").append(field.getName()).append(" ").append(StringUtils.uncapitalize(field.getName())).append(";");
            });
        }
        // 方法处理开始
        sb.append(

                getMethodDefinitions().

                        stream().

                        map(MethodDefinition::toString).

                        collect(Collectors.joining("\n")));

        sb.append("}");
        return sb.toString();
    }

}
